/* This File is part of the Little Miss Engine Project, a simplistic free 3D engine
 *
 * copyright : Brunie Nicolas (2010 - 2011)
 *
 */ 
#include <cmath>
#include "SFML/Graphics.hpp"
#include "utils.hpp"
#include "Selection.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_access.hpp>
#include <vector>

#ifndef __HITBOX_HPP__
#define __HITBOX_HPP__

/** extract a 3 dimensions glm vector for a glm vector with 4 dimensions */
glm::vec3 get_vec3(glm::vec4 v);


/** Basic entity to test collision, the hitbox is a parallelepiped	defined by a point
 * 	and 3 directions vectors */
class HitBox {
  public:
	/** hitbox first point */
    glm::vec3 m_corner;

	/** hitbox first dimension, that defines the second point
	 *	from the first one */
    glm::vec3 m_dim1;

	/** hitbox second dimension, that defines the third point
	 *	from the first one */
    glm::vec3 m_dim2;

	/** hitbox third dimension that defines the fourth point
	 *	from the first one */
    glm::vec3 m_dim3;
  public:
	/** empty hitbox builder */
    HitBox() {};

	/** hitbox builder from its first corner and the 3 dimension vectors */
	HitBox(vec3 corner, vec3 dim1, vec3 dim2, vec3 dim3) {
		m_corner = corner; m_dim1 = dim1; m_dim2 = dim2; m_dim3 = dim3;
	};


	/** display the hitbox on screen as a wireframe polygon
	 *	used for debug purpose */
    void render();

	/** return the selection (convex envelopp projection on screen) of the HitBox */
	Selection* getSelection();

	/** return an xbox with position matrix change applied 
	 * param[in] positionMatrix matrix to apply to each point to now its current location
	 */
	HitBox getMovedHitBox(glm::mat4 positionMatrix);

	/** Move the box according to positionMatrix and then test if point is inside the box */
	bool collidePoint(vec3 point, glm::mat4 positionMatrix);

	/** Test if point is inside the box at its current position */
	bool collidePoint(vec3 point);

	/** move the hitbox according to positionMatrix and then test if this hitbox collides
	 *	with the hitbox hb given as argument
	 *	param[in] hb hitbox with which the collision is tested
	 *	param[in] positionMatrix movement matrix use to determine a new position for this hitbox before collision test
	 */
	bool collideHitBox(HitBox hb, glm::mat4 positionMatrix);
	/** Determine wheter or not this hitbox collides with the hitbox hb */
	bool collideHitBox(HitBox hb);
};

/** General HitBoxes Class, allow the program to group
 *	several hitboxes and test them like one hitbox */
class HitBoxes {
	private:
		/** internal structure to store link existing between vertices */
		bool* m_edge;

		/** internal structure to store state of vertex : used or unused */
		bool* m_used;

		/** number of vertices */
		int n;

		/** vertices coords */
		float* m_coords;

		/** HitBox */
		list<HitBox> m_hitboxList;
	public:
		/** debug function : return the corners of the first hitbox in the hitbox list */
		vec3 corner() {
			return (*(m_hitboxList.begin())).m_corner;
		};
		/** inititialize internal data structure to parse a vertex list into a hitbox list
		 * param[in] n_ number of vertices that would be treated by this hitbox builder
		 */
		void init(int n_) {
			n = n_;
			m_used = new bool[n];
			for (int i = 0; i < n; i++) {
				m_used[i] = false;
			};
			m_edge = new bool[n * n];
			cout << n * n << " ll" << endl;
			for (int i = 0; i < n * n; i++) m_edge[i] = false;

			m_coords = new float[3 * n];
		};
		
		/** function for setting vertex coords */
		void setCoords(float* vertices) {
			m_coords = vertices;
		};

		/** function to add a link to internal data structure
         * param[in] i index of the first vertex of the link
		 * param[in] j index of the second vertex of the link
		 */
		void addLink(int i, int j) {
			m_edge[i*n+j] = true;
			m_edge[j*n+i] = true;
		};

		/** function to render hitboxes */
		void render() {
			list<HitBox>::iterator it;	
			for (it = m_hitboxList.begin(); it != m_hitboxList.end(); it++) {
				(*it).render();
			};
		}

		/** function used to propagate information into the internal data structure
         *  : progate the using of vertex indexed by i in order to mark as used all 
		 *  its neighbours
		 * param[in] i index of input vertex
         */
		void propagateUse(int i) {
			if (m_used[i]) return;
			m_used[i] = true;
			for (int j = 0; j < n; j++) {
				if (m_edge[i*n+j]) propagateUse(j);
			};
		};


		/** function to create hitbox with the list of vertex described
		 *  by the link added through the addLink function
		 *  to be called once, after all links have been added
         */
		void createHitBox() {
			cout << "createHitBox" << endl;
			//list<HitBox> tmp;
			for (int i = 0; i < n; i++) {
				if (m_used[i]) continue;
				int v[3];
				int k = 0;
				// on cherche les 3 sommets
				for (int j = i+1; j < n; j++) 
					if (m_edge[i*n+j] and k < 3) {
						v[k++] = j;
					};
				// on met use dans toutes la composante connexe
				propagateUse(i);
				// on enregistre la HitBox
				glm::vec3 v0,v1,v2,v3;
				//float* = m_coords+3*i;
				v0 = glm::vec3(m_coords[3*i], m_coords[3*i+1], m_coords[3*i+2]);
				v1 = glm::vec3(m_coords[3*v[0]], m_coords[3*v[0]+1], m_coords[3*v[0]+2]);
				v2 = glm::vec3(m_coords[3*v[1]], m_coords[3*v[1]+1], m_coords[3*v[1]+2]);
				v3 = glm::vec3(m_coords[3*v[2]], m_coords[3*v[2]+1], m_coords[3*v[2]+2]);
				m_hitboxList.push_back(HitBox(v0,v1 - v0, v2 - v0, v3 - v0));
			}; 
		};
		void addHitBox(HitBox hb) { m_hitboxList.push_back(hb);};

		/** function to compute the selection (convex envelopp of the hitboxes
		 *   projected on the screen) 
		 *  param[in] changeMatrix transformation matrix to apply to a point 
		 * 				in order to get its current position before projecting it
		 */
		Selection* getSelection(glm::mat4 positionMatrix);

		bool collideHitBox(HitBox hb) {
			list<HitBox>::iterator it;
			for (it = m_hitboxList.begin(); it != m_hitboxList.end(); it++) {
				if ((*it).collideHitBox(hb)) return true;
			};
			return false;
		};

		bool collideHitBoxes(HitBoxes hb) {
			list<HitBox>::iterator it;
			for (it = m_hitboxList.begin(); it != m_hitboxList.end(); it++) {
				if (hb.collideHitBox(*it)) return true;
			};
			return false;
		};

		bool collidePoint(vec3 point) {
			list<HitBox>::iterator it;
			for (it = m_hitboxList.begin(); it != m_hitboxList.end(); it++) {
				if ((*it).collidePoint(point)) return true;
			};
			return false;
		};

		HitBoxes* getMovedHitBoxes(glm::mat4 positionMatrix){
			// creation of a new HitBoxes, TODO : to be deleted after use
			HitBoxes* hb = new HitBoxes();
			list<HitBox>::iterator it;
			for (it = m_hitboxList.begin(); it != m_hitboxList.end(); it++) {
				hb->addHitBox((*it).getMovedHitBox(positionMatrix));
			};
			return hb;
		}
};

#endif
